#ifndef CORE_COMMANS_H_INCLUDED
#define CORE_COMMANS_H_INCLUDED

#include <Core/Proto/Protocols.h>
#include <Core/SyncedCommandIOStream.h>
#include <Common/Detail/FileInputDevice.h>

#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/noncopyable.hpp>

#define ADD_COMMAND_TAG(value) static int const tag = value; 

namespace Core {

namespace Aux {

/**
 * Command type tags values
 */
enum CommandTags {
	UNKNOWN_COMMAND = 0x0,
	FILE_SCAN_TAG 	= 0x1,
	ARRAY_SCAN_TAG 	= 0x2,
	PING_TAG 		= 0x3,
	OK_TAG 			= 0x4,
	GUID_TAG 		= 0x5,
	ERROR_TAG 		= 0x6,
};

} // namespace Aux

/**
 * Scaner Abstract command
 */
class ICommand : private boost::noncopyable {
public :
	typedef boost::function<void(int)> CommandCompeletionFunc;

	explicit ICommand(int tag) : tag__(tag), m_IOFace() { }
	virtual ~ICommand() { }
	
	inline int getTag() const 
		{ return tag__; }	
	
	inline void setIOFace(SyncedCommandIOStreamPtr IOFace) 
		{ m_IOFace = IOFace; }

	inline SyncedCommandIOStreamPtr getIOIFace() 
		{ return m_IOFace; }

	/**
	 * Optional interface methods
	 */
	virtual void execute()  { }
	virtual void interrupt() { }

private :
	int const tag__;
	SyncedCommandIOStreamPtr m_IOFace;

};

typedef boost::shared_ptr<ICommand> ICommandPtr;

/**
 * Commands Helpers
 */
template <class Command>
inline boost::shared_ptr<Command> commandCast(ICommandPtr command) 
{
	if (command)
		return command->getTag() == Command::tag ? 
			boost::static_pointer_cast<Command>(command) : boost::shared_ptr<Command>();
	return boost::shared_ptr<Command>();
}

bool sendCommand(SyncedCommandIOStreamPtr stream, ICommandPtr command);

/**
 * Serializetions Helpers (underlying protocol logic)
 */
namespace CommandCreateAdapters {

template <class Translator>
inline ICommandPtr unPack(SyncedCommandIOStreamPtr commandStream) 
{
	if (commandStream) {
		Translator tr;
		return tr.decode(commandStream);
	}
	return ICommandPtr();
}

template <class Translator>
inline ICommandPtr unPackReply(SyncedCommandIOStreamPtr commandStream) 
{
	if (commandStream) {
		Translator tr;
		return tr.decodeReply(commandStream);
	}
	return ICommandPtr();
}

template <class Translator>
inline bool pack(SyncedCommandIOStreamPtr commandStream, ICommandPtr command) 
{
	if (commandStream && command) {
		Translator tr;
		return tr.encode(commandStream, command);
	}
	return false;
}

} // namespace CommandCreateAdapters

/**
 * Erorr Command implementation
 */
class ErrorCommand : public ICommand {
public :
	ADD_COMMAND_TAG(Aux::ERROR_TAG)
	enum ErrorStates {
		INTERNAL_SERVER_ERROR = 0x0,
		FILE_NO_FOUND = 0x1,
		CONNECTION_BROKEN = 0x2,
		INTERNAL_PROTOCOL_ERROR = 0x3
	};

	ErrorCommand(std::string const & message, int errorCode) : 
		ICommand(Aux::ERROR_TAG),
		m_message(message), 
		m_errorCode(errorCode)	
	{ 
	}

	inline std::string const & getMessage() const 
		{ return m_message; }
	
	inline int getErrorCode() const 
		{ return m_errorCode; }

private :
	std::string const m_message;
	int const m_errorCode;

};

typedef boost::shared_ptr<ErrorCommand> ErrorCommandPtr;

/**
 * Ok Command implemntation 
 */
class OkCommand : public ICommand {
public :
	ADD_COMMAND_TAG(Aux::OK_TAG)
	OkCommand() : ICommand(Aux::OK_TAG) { }
};

typedef boost::shared_ptr<OkCommand> OkCommandPtr;

/**
 * GUID Command implementation
 */
class GuidReplyCommand : public ICommand {
public :
	ADD_COMMAND_TAG(Aux::GUID_TAG)

	explicit GuidReplyCommand(std::string const & guid) :
		ICommand(Aux::GUID_TAG),
		m_guid(guid) 
	{ 
	}
	
	inline std::string const & getGuid() const 
		{ return m_guid; }

private :
	std::string const m_guid;

};

typedef boost::shared_ptr<GuidReplyCommand> GuidReplyCommandPtr;

/**
 * Scan file command 
 */
class FileScanCommand : public ICommand 
{
public :
	ADD_COMMAND_TAG(Aux::FILE_SCAN_TAG)

	explicit FileScanCommand(std::string const & filePath) 
		: ICommand(Aux::FILE_SCAN_TAG), m_filePath(filePath)
	{ 
	}
		
	virtual ~FileScanCommand() { }
	
	virtual void execute() 
	{ 
#if defined(SS_DISABLE_SEARCH_ENGINE)
		try 
		{
		 	SearchEngine sarchEng;
			IKeyValueStoragePtr keyValueStorage = defaultMapKeyValueStorade();
			if (keyValueStorage) {
				Common::FileInputDevicePtr fileStream(new Common::FileInputDevice());
				fileStream->init(m_filePath->path);
				std::string const uiid = searchEng->searchStart(fileStream, keyValueStorage);
				sendCommand(getIOIFace(), GuidReplyCommandPtr(new GuidReplyCommand(uiid)));
				return;
			} else {
				sendCommand(getIOIFace(), ErrorCommandPtr(
					new (std::nothrow) ErrorCommand("Key value DB not initialized", 
						ErrorCommand::INTERNAL_SERVER_ERROR)));
			}
		}
		catch (std::exception const & expt) 
		{
			sendCommand(getIOIFace(), ErrorCommandPtr(
				new (std::nothrow) ErrorCommand(expt.what(), ErrorCommand::INTERNAL_SERVER_ERROR)));
		} 
#else
#	warning Search engine logic is off!
		sendCommand(getIOIFace(), ErrorCommandPtr(new (std::nothrow) 
			ErrorCommand("Search engine is off!", ErrorCommand::INTERNAL_SERVER_ERROR)));
#endif
	}
	
	inline std::string const & getFilePath() const 
		{ return m_filePath; }

private :
	std::string const m_filePath;

};

typedef boost::shared_ptr<FileScanCommand> FileScanCommandPtr;

/**
 * Scan byte array command 
 */
class ArrayScanCommand : public ICommand {
public :
	ADD_COMMAND_TAG(Aux::ARRAY_SCAN_TAG)

	ArrayScanCommand(std::size_t chunkSize, boost::int64_t arraySize) : 
		ICommand(Aux::ARRAY_SCAN_TAG),
		m_chunkSize(chunkSize), 
		m_arraySize(arraySize) 
	{ 
	}

	virtual ~ArrayScanCommand() { }
	
	virtual void execute() 
	{ 
#if 0
/*		try 
		{
		 	SearchEnginePtr sarchEng = SearchEngine::get(); 
			std::string const uiid = searchEng->searchStart(m_filePath, getIOIFace());
			sendCommand(getIOIFace(), GuidReplyCommandPtr(new (std::nothrow) GuidReplyCommand(uiid)));
		}
		catch (std::exception const & expt) 
		{
			sendCommand(getIOIFace(), ErrorCommandPtr(new (std::nothrow) ErrorCommand(expt.what(), 0)));
		} */
#else
#	warning Search engine logic is off!
		sendCommand(getIOIFace(), ErrorCommandPtr(new (std::nothrow) 
			ErrorCommand("Search engine is off!", ErrorCommand::INTERNAL_SERVER_ERROR)));
#endif

	}

	inline std::size_t getChunkSize() const 
		{ return m_chunkSize; }

	inline boost::int64_t getArraySize() const 
		{ return m_arraySize; }
	
private :
	std::size_t const m_chunkSize;
	boost::int64_t m_arraySize;

};

typedef boost::shared_ptr<ArrayScanCommand> ArrayScanCommandPtr;

/**
 * Ping Command
 */
class PingCommand : public ICommand {
public :
	ADD_COMMAND_TAG(Aux::PING_TAG)

	PingCommand() : ICommand(Aux::PING_TAG) { }
	
	virtual ~PingCommand() { }
	
	virtual void execute() 
	{ 
		if (!sendCommand(getIOIFace(), OkCommandPtr(new OkCommand()))) {
			// TODO log this ?!
		}
	}
};

typedef boost::shared_ptr<PingCommand> PingCommandPtr;

/**
 * ProtobufCommandTranslator : translate incomming Google protobuf data to Command.
 * Also this class write translated data to SyncedCommandIOStream.
 */
class ProtobufCommandTranslator {
public :
	typedef Core::Proto::GoogleBufferProtoTranslator<Core::Proto::GoogleProtoCommand> TranslatorType;
	typedef boost::function<TranslatorType::MessagePtr (TranslatorType &, SyncedCommandIOStreamPtr)> TranslateFunc;
	typedef Core::Proto::Detail::Encoding<TranslatorType, SyncedCommandIOStreamPtr> Encoding;

	ProtobufCommandTranslator() { }
	~ProtobufCommandTranslator() { }

	inline ICommandPtr decode(SyncedCommandIOStreamPtr stream) 
		{ return decodeGeneric(stream, boost::bind(&Encoding::encodeFromStream, _1, _2)); }
	
	inline ICommandPtr decodeReply(SyncedCommandIOStreamPtr stream) 
		{ return decodeGeneric(stream, boost::bind(&Encoding::encodeFromStreamReply, _1, _2)); }

	bool encode(SyncedCommandIOStreamPtr stream, ICommandPtr command);
	ICommandPtr decodeGeneric(SyncedCommandIOStreamPtr stream, TranslateFunc tf);
	
private :
	bool encodeAndWrite(SyncedCommandIOStreamPtr stream);

	bool fileScanCommandEncode(SyncedCommandIOStreamPtr stream, FileScanCommandPtr command);
	bool arrayScanCommandEncode(SyncedCommandIOStreamPtr stream, ArrayScanCommandPtr command);
	bool pingScanCommandEncode(SyncedCommandIOStreamPtr stream, PingCommandPtr command); 
	bool okScanCommandEncode(SyncedCommandIOStreamPtr stream, OkCommandPtr command);
	bool errorCommandEncode(SyncedCommandIOStreamPtr stream, ErrorCommandPtr command);
	bool guidReplyCommandEncode(SyncedCommandIOStreamPtr stream, GuidReplyCommandPtr command);

	TranslatorType m_translator;

};

} // namespace Core

#undef ADD_COMMAND_TAG

#endif

